// Helper functions ----------------------------------------------------------
mata:

`Void' _assert_abort(`Integer' rc, `String' msg, `Boolean' traceback) {
	if (traceback) {
		_error(rc, msg)
	}
	else {
		printf("{err}%s\n", msg)
		exit(rc) // exit(error(rc))
	}
}


`Void' assert_msg(`Boolean' t, | `String' msg, `Integer' rc, `Boolean' traceback)
{
	if (args()<2 | msg=="") msg = "assertion is false"
	if (args()<3 | rc==.) rc = 3498
	if (args()<4 | rc==.) traceback = 1
	if (t==0) _assert_abort(rc, msg, traceback)
}


`Void' assert_in(`DataCell' value, `DataRow' valid_values, | string scalar msg)
{
	if (args()<2 | msg=="") msg = "assertion is false; value not in list"
	// "anyof(valid_values, value)"  <==> "value in valid_values" [Python]
	if (!anyof(valid_values, value)) _error(msg)
}


`Void' assert_boolean(`DataCell' value, | string scalar msg)
{
	if (args()<2 | msg=="") msg = "assertion is false; value not boolean"
	assert_in(value, (0,1), msg)
}


// mask: a dummy variable indicating selection (like -touse-)
// Example usage:
// mata: idx = (1,2,5,9)'
// mata: m = create_mask(12, 0, idx, 1)
// mata: update_mask(m, idx, 2)
// mata: update_mask(m, (1,3)', 10)

`Void' update_mask(`Variable' mask, `Vector' index, `Real' value)
{
	if (!length(index)) return

	// Allow for vector and rowvector masks
	if (is_rowvector(index)) {
		mask[index] = J(1, cols(index), value)
	}
	else {
		mask[index] = J(rows(index), 1, value)
	}
}


`Variable' create_mask(`Integer' obs, `Real' default_value, `Vector' index, `Real' value)
{
	`Variable'		mask
	// Allow for vector and rowvector masks
	if (is_rowvector(index)) {
		mask = J(1, obs, default_value)
	}
	else {
		mask = J(obs, 1, default_value)
	}
	update_mask(mask, index, value)
	return(mask)
}


`Real' clip(`Real' x, `Real' min_x, `Real' max_x) {
	return(x < min_x ? min_x : (x > max_x ? max_x : x))
}


`Matrix' inrange(`Matrix' x, `Matrix' lb, `Matrix' ub)
{
	return(lb :<= x :& x :<= ub)
}


`Boolean' is_rowvector(`DataFrame' x) {
	return(orgtype(x) == "rowvector")
}


// Return 1 if all the variables are integers 
`Boolean' varlist_is_integers(`Varlist' varlist, `DataFrame' data)
{
	`Integer' 				i
	`Integer' 				num_vars
	`String'				type

	if (eltype(data) == "string") {
		return(0)
	}

	num_vars = cols(varlist)
	for (i = 1; i <= num_vars; i++) {
		type = st_vartype(varlist[i])
		if (anyof(("byte", "int", "long"), type)) {
			continue
		}
		if (round(data[., i])==data[., i]) {
				continue
		}
		return(0)
	}
	return(1)
}


// Return 1 if the varlist has string and numeric types
`Boolean' varlist_is_hybrid(`Varlist' varlist)
{
	`Boolean' 				first_is_num
	`Integer' 				i
	`Integer' 				num_vars

	num_vars = cols(varlist)
	first_is_num = st_isnumvar(varlist[1])
	for (i = 2; i <= num_vars; i++) {
		if (first_is_num != st_isnumvar(varlist[i])) {
			return(1)
			//_error(999, "variables must be all numeric or all strings")
		}
	}
	return(0)
}


`DataFrame' __fload_data(`Varlist' varlist,
                       | `DataCol' touse,
                         `Boolean' touse_is_selectvar)
{
	`Integer'				num_vars
	`Boolean'				is_num
	`Integer'				i
	`DataFrame'				data

	if (args()<2) touse = .
	if (args()<3) touse_is_selectvar = 1 // can be selectvar (a 0/1 mask) or an index vector

	varlist = tokens(invtokens(varlist)) // accept both types
	assert_msg(!varlist_is_hybrid(varlist), "variables must be all numeric or all strings", 999)
	is_num = st_isnumvar(varlist[1])

	//     idx   = touse_is_selectvar ?   .   : touse
	// selectvar = touse_is_selectvar ? touse :   .
	if (is_num) {
		data =  st_data(touse_is_selectvar ? . : touse , varlist, touse_is_selectvar ? touse : .)
	}
	else {
		data = st_sdata(touse_is_selectvar ? . : touse , varlist, touse_is_selectvar ? touse : .)
	}
	return(data)
}


`Void' __fstore_data(`DataFrame' data,
                     `Varname' newvar,
                     `String' type,
                   | `String' touse)
{
	`RowVector'				idx
	idx = st_addvar(type, newvar)
	if (substr(type, 1, 3) == "str") {
		if (touse == "") st_sstore(., idx, data)
		else st_sstore(., idx, touse, data)
	}
	else {
		if (touse == "") st_store(., idx, data)
		else st_store(., idx, touse, data)
	}
}


// Based on Nick Cox's example
// https://www.statalist.org/forums/forum/general-stata-discussion/general/1330558-product-of-row-elements?p=1330561#post1330561
`Matrix' rowproduct(`Matrix' X)
{
	`Integer' i, k
	`Matrix' prod
	k = cols(X)
	if (k==1) return(X)
	prod = X[,1]
	for(i = 2; i<=k; i++) {
		prod = prod :* X[,i]
	}
	return(prod)
}



`Void' unlink_folder(`String' path, `Boolean' verbose)
{
	// We are SUPER careful in only removing certain files... so if there are other files this function will fail
	`StringVector'			fns, patterns
	`Integer'				i, j, num_dropped
	
	if (!direxists(path)) exit()
	if (verbose) printf("{txt}Removing folder and its contents: {res}%s{txt}\n", path)
	
	num_dropped = 0
	patterns = ("*.tmp" \ "*.log" \ "parallel_code.do")

	for (j=1; j<=rows(patterns); j++){
		fns = dir(path, "files", patterns[j], 1)
		for (i=1; i<=rows(fns); i++) {
			unlink(fns[i])
			++num_dropped
		}
	}

	if (verbose) printf("{txt} - %f files removed\n", num_dropped)
	
	rmdir(path)
	if (verbose) printf("{txt} - Folder removed\n")
}


end
